/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); You may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.speedment.maven.abstractmojo;
import com.speedment.common.injector.InjectBundle;
import com.speedment.common.injector.Injector;
import static com.speedment.common.injector.State.INITIALIZED;
import static com.speedment.common.injector.State.RESOLVED;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.InjectKey;
import com.speedment.common.injector.annotation.WithState;
import com.speedment.generator.core.GeneratorBundle;
import com.speedment.generator.translator.component.TypeMapperComponent;
import com.speedment.generator.translator.internal.component.CodeGenerationComponentImpl;
import com.speedment.maven.component.MavenPathComponent;
import static com.speedment.maven.component.MavenPathComponent.MAVEN_BASE_DIR;
import com.speedment.maven.parameter.ConfigParam;
import com.speedment.maven.typemapper.Mapping;
import com.speedment.runtime.core.ApplicationBuilder;
import com.speedment.runtime.core.Speedment;
import static com.speedment.runtime.core.internal.DefaultApplicationMetadata.METADATA_LOCATION;
import com.speedment.runtime.typemapper.TypeMapper;
import com.speedment.tool.core.ToolBundle;
import com.speedment.tool.core.internal.component.UserInterfaceComponentImpl;
import static com.speedment.tool.core.internal.util.ConfigFileHelper.DEFAULT_CONFIG_LOCATION;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
/**
* The abstract base implementation for all the Speedment Mojos.
*
* @author Emil Forslund
*/
public abstract class AbstractSpeedmentMojo extends AbstractMojo {
private static final Path DEFAULT_CONFIG = Paths.get(DEFAULT_CONFIG_LOCATION);
private static final Consumer<ApplicationBuilder<?, ?>> NOTHING = builder -> {};
private final Consumer<ApplicationBuilder<?, ?>> configurer;
AbstractSpeedmentMojo() {this(NOTHING);}
AbstractSpeedmentMojo(Consumer<ApplicationBuilder<?, ?>> configurer) {
this.configurer = requireNonNull(configurer);
}
protected abstract MavenProject project();
protected abstract boolean debug();
protected abstract String dbmsHost();
protected abstract int dbmsPort();
protected abstract String dbmsUsername();
protected abstract String dbmsPassword();
protected abstract String[] components();
protected abstract Mapping[] typeMappers();
protected abstract ConfigParam[] parameters();
protected abstract String launchMessage();
protected abstract String getConfigFile();
protected abstract void execute(Speedment speedment)
throws MojoExecutionException, MojoFailureException;
protected Path configLocation() {
final String top = getConfigFile() == null
? DEFAULT_CONFIG_LOCATION
: getConfigFile();
System.out.println(project());
return project()
.getBasedir()
.toPath()
.resolve(top);
}
@Override
public final void execute() throws MojoExecutionException, MojoFailureException {
final ApplicationBuilder<?, ?> builder = createBuilder();
builder.withComponent(MavenPathComponent.class);
builder.withParam(MAVEN_BASE_DIR, project().getBasedir().toString());
configurer.accept(builder);
if (debug()) {
builder.withLogging(ApplicationBuilder.LogType.APPLICATION_BUILDER);
}
builder.withSkipCheckDatabaseConnectivity();
final Speedment speedment = builder.build();
getLog().info(launchMessage());
execute(speedment);
}
/**
* Returns {@code true} if the default configuration file is non-null,
* exists and is readable. If, not, {@code false} is returned and an
* appropriate message is shown in the console.
*
* @return {@code true} if available, else {@code false}
*/
protected final boolean hasConfigFile() {
return hasConfigFile(configLocation());
}
/**
* Returns if the specified file is non-null, exists and is readable. If,
* not, {@code false} is returned and an appropriate message is shown in the
* console.
*
* @param file the config file to check
* @return {@code true} if available, else {@code false}
*/
protected final boolean hasConfigFile(Path file) {
if (file == null) {
final String msg = "The expected .json-file is null.";
getLog().info(msg);
return false;
} else if (!Files.exists(file)) {
final String msg = "The expected .json-file '"
+ file + "' does not exist.";
getLog().info(msg);
return false;
} else if (!Files.isReadable(file)) {
final String err = "The expected .json-file '"
+ file + "' is not readable.";
getLog().error(err);
return false;
} else {
return true;
}
}
@SuppressWarnings("unchecked")
protected final ClassLoader getClassLoader()
throws MojoExecutionException, DependencyResolutionRequiredException {
final MavenProject project = project();
final List<String> classpathElements = new ArrayList<>();
classpathElements.addAll(project.getCompileClasspathElements());
classpathElements.addAll(project.getRuntimeClasspathElements());
classpathElements.add(project.getBuild().getOutputDirectory());
final List<URL> projectClasspathList = new ArrayList<>();
for (final String element : classpathElements) {
try {
projectClasspathList.add(new File(element).toURI().toURL());
} catch (final MalformedURLException ex) {
throw new MojoExecutionException(
element + " is an invalid classpath element", ex
);
}
}
return new URLClassLoader(
projectClasspathList.toArray(new URL[projectClasspathList.size()]),
Thread.currentThread().getContextClassLoader()
);
}
private ApplicationBuilder<?, ?> createBuilder() throws MojoExecutionException {
final ApplicationBuilder<?, ?> result;
// Create the ClassLoader
final ClassLoader classLoader;
try {
classLoader = getClassLoader();
} catch (final DependencyResolutionRequiredException ex) {
throw new MojoExecutionException(ex.toString(), ex);
}
// Configure config file location
if (hasConfigFile()) {
result = ApplicationBuilder.standard(classLoader)
.withParam(METADATA_LOCATION, configLocation().toAbsolutePath().toString());
} else if (hasConfigFile(DEFAULT_CONFIG)) {
result = ApplicationBuilder.standard(classLoader)
.withParam(METADATA_LOCATION, DEFAULT_CONFIG_LOCATION);
} else {
result = ApplicationBuilder.empty(classLoader);
}
//
result.withSkipCheckDatabaseConnectivity();
// Configure manual database settings
if (dbmsHost() != null) {
result.withIpAddress(dbmsHost());
getLog().info("Custom database host '" + dbmsHost() + "'.");
}
if (dbmsPort() != 0) {
result.withPort(dbmsPort());
getLog().info("Custom database port '" + dbmsPort() + "'.");
}
if (dbmsUsername() != null) {
result.withUsername(dbmsUsername());
getLog().info("Custom database username '" + dbmsUsername() + "'.");
}
if (dbmsPassword() != null) {
result.withPassword(dbmsPassword());
getLog().info("Custom database password '********'.");
}
// Add mandatory components that are not included in 'runtime'
result
.withBundle(GeneratorBundle.class)
.withBundle(ToolBundle.class)
.withComponent(CodeGenerationComponentImpl.class)
.withComponent(UserInterfaceComponentImpl.class)
.withComponent(MavenPathComponent.class);
// Add any extra type mappers requested by the user
TypeMapperInstaller.mappings = typeMappers(); // <-- Hack to pass type mappers to class with default constructor.
result.withComponent(TypeMapperInstaller.class);
// Add extra components requested by the user
final String[] components = components();
if (components != null) {
for (final String component : components) {
try {
final Class<?> uncasted = classLoader.loadClass(component);
if (InjectBundle.class.isAssignableFrom(uncasted)) {
@SuppressWarnings("unchecked")
final Class<? extends InjectBundle> casted
= (Class<? extends InjectBundle>) uncasted;
result.withBundle(casted);
} else {
result.withComponent(uncasted);
}
} catch (final ClassNotFoundException ex) {
throw new MojoExecutionException(
"Specified class '" + component + "' could not be "
+ "found on class path. Has the dependency been "
+ "configured properly?", ex
);
}
}
}
// Set parameters configured in the pom.xml
final ConfigParam[] parameters = parameters();
if (parameters != null) {
for (final ConfigParam param : parameters()) {
result.withParam(param.getName(), param.getValue());
}
}
// Return the resulting builder.
return result;
}
private final static class TypeMapperInstantiationException extends RuntimeException {
private static final long serialVersionUID = -8267239306656063289L;
private TypeMapperInstantiationException(Throwable thrw) {
super(thrw);
}
}
@InjectKey(TypeMapperInstaller.class)
private final static class TypeMapperInstaller {
private static Mapping[] mappings;
@ExecuteBefore(RESOLVED)
void installInTypeMapper(
final Injector injector,
final @WithState(INITIALIZED) TypeMapperComponent typeMappers
) throws MojoExecutionException {
if (mappings != null) {
for (final Mapping mapping : mappings) {
final Class<?> databaseType;
try {
databaseType = injector.classLoader()
.loadClass(mapping.getDatabaseType());
} catch (final ClassNotFoundException ex) {
throw new MojoExecutionException(
"Specified database type '" + mapping.getDatabaseType() + "' "
+ "could not be found on class path. Make sure it is a "
+ "valid JDBC type for the choosen connector.", ex
);
} catch (final ClassCastException ex) {
throw new MojoExecutionException(
"An unexpected ClassCastException occured.", ex
);
}
try {
final Class<?> uncasted = injector.classLoader()
.loadClass(mapping.getImplementation());
@SuppressWarnings("unchecked")
final Class<TypeMapper<?, ?>> casted
= (Class<TypeMapper<?, ?>>) uncasted;
final Constructor<TypeMapper<?, ?>> constructor
= casted.getConstructor();
final Supplier<TypeMapper<?, ?>> supplier = () -> {
try {
return constructor.newInstance();
} catch (final IllegalAccessException
| IllegalArgumentException
| InstantiationException
| InvocationTargetException ex) {
throw new TypeMapperInstantiationException(ex);
}
};
typeMappers.install(databaseType, supplier);
} catch (final ClassNotFoundException ex) {
throw new MojoExecutionException(
"Specified class '" + mapping.getImplementation()
+ "' could not be found on class path. Has the "
+ "dependency been configured properly?", ex
);
} catch (final ClassCastException ex) {
throw new MojoExecutionException(
"Specified class '" + mapping.getImplementation()
+ "' does not implement the '"
+ TypeMapper.class.getSimpleName() + "'-interface.",
ex
);
} catch (final NoSuchMethodException
| TypeMapperInstantiationException ex) {
throw new MojoExecutionException(
"Specified class '" + mapping.getImplementation()
+ "' could not be instantiated. Does it have a "
+ "default constructor?", ex
);
}
}
}
}
}
}